<?php

// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2025-04-25 10:54:44
// |@----------------------------------------------------------------------
// |@LastEditTime : 2025-04-29 16:33:27
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : 服务类库
// |@----------------------------------------------------------------------
// |@FilePath     : Service.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2025 http://www.ladmin.cn   All rights reserved.
// ------------------------------------------------------------------------

declare(strict_types=1);

namespace think\addons;

use think\Route;
use think\helper\Str;
use think\addons\middleware\Addons;
use RuntimeException;

/**
 * 插件服务类
 *
 * 负责插件的注册、加载和管理
 * 包括插件路由、事件、服务、命令等功能的注册和加载
 *
 * @package think\addons
 */
class Service extends \think\Service
{
    /**
     * 缓存键前缀
     */
    private const CACHE_PREFIX = 'addons_route_';

    /**
     * 中间件文件
     */
    private const MIDDLEWARE_FILE = 'middleware.php';

    /**
     * 路由执行方法
     */
    private const ROUTE_EXECUTE = '\\think\\addons\\Route::execute';

    /**
     * 插件路径
     * @var string
     */
    protected string $addons_path;

    /**
     * 注册服务
     *
     * 初始化插件系统，包括：
     * 1. 设置插件路径
     * 2. 加载配置
     * 3. 注册自动加载器
     * 4. 加载语言包
     * 5. 加载插件组件
     * 6. 绑定插件容器
     *
     * @throws \Exception 注册失败时抛出异常
     */
    public function register(): void
    {
        try {
            $this->addons_path = $this->getAddonsPath();
            $this->loadConfig();
            $this->registerAutoloader();
            $this->loadLang();
            $this->autoload();
            $this->loadEvent();
            $this->loadService();
            $this->loadCommand();
            $this->app->bind('addons', Service::class);
        } catch (\Exception $e) {
            $this->app->log->error('Addons service register failed: ' . $e->getMessage());
            throw $e;
        }
    }

    /**
     * 启动服务
     *
     * 注册插件路由和中间件
     */
    public function boot(): void
    {
        $this->registerRoutes(function (Route $route) {
            if (strpos($this->app->request->pathinfo(), 'addons/') === 0) {
                $this->registerAddonRoutes($route);
            }
        });
    }

    /**
     * 注册插件路由
     *
     * 包括：
     * 1. 注册插件公共中间件
     * 2. 注册插件控制器路由
     * 3. 注册自定义路由
     *
     * @param Route $route 路由实例
     * @throws \RuntimeException 路由注册失败时抛出异常
     */
    private function registerAddonRoutes(Route $route): void
    {
        try {
            // 注册插件公共中间件
            $this->registerAddonMiddleware();

            // 注册控制器路由
            $route->rule('addons/:addon/[:controller]/[:action]', self::ROUTE_EXECUTE)
                ->middleware(Addons::class);

            // 注册自定义路由
            $this->registerCustomRoutes($route, self::ROUTE_EXECUTE);
        } catch (\Exception $e) {
            $this->app->log->error('Addons route registration failed: ' . $e->getMessage());
            throw new \RuntimeException('Addons route registration failed: ' . $e->getMessage());
        }
    }

    /**
     * 注册插件公共中间件
     *
     * @throws \RuntimeException 中间件加载失败时抛出异常
     */
    private function registerAddonMiddleware(): void
    {
        try {
            $cacheKey = self::CACHE_PREFIX . 'middleware';
            $middleware = null;

            try {
                $middleware = $this->app->cache->get($cacheKey);
            } catch (\Exception $e) {
                $this->app->log->warning('Middleware cache read failed: ' . $e->getMessage());
            }

            if ($middleware === null) {
                $middlewareFile = $this->app->addons->getAddonsPath() . self::MIDDLEWARE_FILE;
                if (is_file($middlewareFile)) {
                    try {
                        $middleware = include $middlewareFile;
                        if (!is_array($middleware)) {
                            throw new \RuntimeException('Middleware file must return an array');
                        }

                        try {
                            $this->app->cache->set($cacheKey, $middleware);
                        } catch (\Exception $e) {
                            $this->app->log->warning('Middleware cache write failed: ' . $e->getMessage());
                        }
                    } catch (\Exception $e) {
                        $this->app->log->error('Middleware file load failed: ' . $e->getMessage());
                        throw new \RuntimeException('Middleware file load failed: ' . $e->getMessage());
                    }
                }
            }

            if (!empty($middleware)) {
                $this->app->middleware->import($middleware, 'route');
            }
        } catch (\Exception $e) {
            $this->app->log->error('Register addon middleware failed: ' . $e->getMessage());
        }
    }

    /**
     * 注册自定义路由
     *
     * @param Route $route 路由实例
     * @param string $execute 路由执行方法
     * @throws \RuntimeException 路由配置无效时抛出异常
     */
    private function registerCustomRoutes(Route $route, string $execute): void
    {
        try {
            $cacheKey = self::CACHE_PREFIX . 'custom_routes';
            $routes = null;

            try {
                $routes = $this->app->cache->get($cacheKey);
            } catch (\Exception $e) {
                $this->app->log->warning('Custom routes cache read failed: ' . $e->getMessage());
            }

            if ($routes === null) {
                $routes = (array) $this->app->config->get('addons.route', []);
                $this->validateRoutes($routes);

                try {
                    $this->app->cache->set($cacheKey, $routes);
                } catch (\Exception $e) {
                    $this->app->log->warning('Custom routes cache write failed: ' . $e->getMessage());
                }
            }

            foreach ($routes as $key => $val) {
                if (!$val) {
                    continue;
                }
                if (is_array($val)) {
                    $this->registerDomainRoutes($route, $val, $execute);
                } else {
                    $this->registerSingleRoute($route, $key, $val, $execute);
                }
            }
        } catch (\Exception $e) {
            $this->app->log->error('Register custom routes failed: ' . $e->getMessage());
        }
    }

    /**
     * 验证路由配置
     *
     * @param array $routes 路由配置
     * @throws \RuntimeException 路由配置无效时抛出异常
     */
    private function validateRoutes(array $routes): void
    {
        foreach ($routes as $key => $val) {
            if (is_array($val)) {
                if (!isset($val['domain']) || !isset($val['rule'])) {
                    throw new \RuntimeException('Invalid domain route configuration');
                }
                if (!is_array($val['rule'])) {
                    throw new \RuntimeException('Domain route rules must be an array');
                }
            } elseif (!is_string($val)) {
                throw new \RuntimeException('Route value must be a string or array');
            }
        }
    }

    /**
     * 注册域名路由
     *
     * @param Route $route 路由实例
     * @param array $domainConfig 域名配置
     * @param string $execute 路由执行方法
     * @throws \RuntimeException 域名路由注册失败时抛出异常
     */
    private function registerDomainRoutes(Route $route, array $domainConfig, string $execute): void
    {
        try {
            $domain = $domainConfig['domain'];
            $rules = [];
            foreach ($domainConfig['rule'] as $k => $rule) {
                if (!is_string($rule)) {
                    throw new \RuntimeException('Domain route rule must be a string');
                }
                [$addon, $controller, $action] = explode('/', $rule);
                if (empty($addon) || empty($controller) || empty($action)) {
                    throw new \RuntimeException('Invalid domain route rule format');
                }
                $rules[$k] = [
                    'addon' => $addon,
                    'controller' => $controller,
                    'action' => $action,
                    'indomain' => 1,
                ];
            }
            $route->domain($domain, function () use ($rules, $route, $execute) {
                foreach ($rules as $k => $rule) {
                    $route->rule($k, $execute)
                        ->name($k)
                        ->completeMatch(true)
                        ->append($rule);
                }
            });
        } catch (\Exception $e) {
            $this->app->log->error('Domain route registration failed: ' . $e->getMessage());
            throw new \RuntimeException('Domain route registration failed: ' . $e->getMessage());
        }
    }

    /**
     * 注册单个路由
     *
     * @param Route $route 路由实例
     * @param string $key 路由键名
     * @param string $val 路由值
     * @param string $execute 路由执行方法
     * @throws \RuntimeException 路由注册失败时抛出异常
     */
    private function registerSingleRoute(Route $route, string $key, string $val, string $execute): void
    {
        try {
            [$addon, $controller, $action] = explode('/', $val);
            if (empty($addon) || empty($controller) || empty($action)) {
                throw new \RuntimeException('Invalid route format');
            }
            $route->rule($key, $execute)
                ->name($key)
                ->completeMatch(true)
                ->append([
                    'addon' => $addon,
                    'controller' => $controller,
                    'action' => $action
                ]);
        } catch (\Exception $e) {
            $this->app->log->error('Route registration failed: ' . $e->getMessage());
            throw new \RuntimeException('Route registration failed: ' . $e->getMessage());
        }
    }

    /**
     * 加载配置
     */
    private function loadConfig(): void
    {
        $configPath = __DIR__ . '/config/addons.php';
        if (is_file($configPath)) {
            $this->app->config->load($configPath, 'addons');
        }
    }

    /**
     * 加载语言包
     */
    private function loadLang(): void
    {
        $langPath = dirname(__DIR__) . '/lang/';
        $this->app->lang->load($langPath . 'zh-cn.php', 'zh-cn');
        $this->app->lang->load($langPath . 'en-us.php', 'en-us');
    }

    /**
     * 插件事件
     */
    private function loadEvent(): void
    {
        try {
            // 尝试使用缓存
            $cacheKey = 'addons_hooks_' . ($this->app->isDebug() ? 'debug' : 'prod');
            $hooks = [];

            try {
                // 尝试从缓存获取
                $hooks = $this->app->cache->get($cacheKey, []);
            } catch (\Exception $e) {
                // 缓存读取失败，记录日志
                $this->app->log->warning('Addons cache read failed: ' . $e->getMessage());
            }

            if (empty($hooks)) {
                $hooks = (array) $this->app->config->get('addons.hooks', []);
                foreach ($hooks as $key => $values) {
                    if (!is_string($key)) {
                        continue;
                    }
                    if (is_string($values)) {
                        $values = explode(',', $values);
                    } else {
                        $values = (array) $values;
                    }
                    $hooks[$key] = array_filter(array_map(function ($v) use ($key) {
                        return [get_addons_class($v), $key];
                    }, $values));
                }

                try {
                    // 尝试写入缓存，但不影响主流程
                    $this->app->cache->set($cacheKey, $hooks, 3600);
                } catch (\Exception $e) {
                    // 缓存写入失败，记录日志
                    $this->app->log->warning('Addons cache write failed: ' . $e->getMessage());
                }
            }

            if (isset($hooks['AddonsInit'])) {
                foreach ($hooks['AddonsInit'] as $k => $v) {
                    $this->app->event->trigger('AddonsInit', $v);
                }
            }
            $this->app->event->listenEvents($hooks);
        } catch (\Exception $e) {
            // 捕获所有异常，确保插件系统不会完全崩溃
            $this->app->log->error('Addons loadEvent failed: ' . $e->getMessage());
        }
    }

    /**
     * 挂载插件服务
     */
    private function loadService(): void
    {
        static $loaded = false;
        if ($loaded) {
            return;
        }
        $results = scandir($this->addons_path);
        $bind = [];
        foreach ($results as $name) {
            if ($name === '.' or $name === '..') {
                continue;
            }
            if (is_file($this->addons_path . $name)) {
                continue;
            }
            $addonDir = $this->addons_path . $name . DIRECTORY_SEPARATOR;
            if (!is_dir($addonDir)) {
                continue;
            }
            if (!is_file($addonDir . ucfirst($name) . '.php')) {
                continue;
            }
            $service_file = $addonDir . 'service.ini';
            if (!is_file($service_file)) {
                continue;
            }
            $info = parse_ini_file($service_file, true, INI_SCANNER_TYPED) ?: [];
            $bind = array_merge($bind, $info);
        }
        $this->app->bind($bind);
        $loaded = true;
    }

    /**
     * 自动载入插件
     * @return bool
     */
    private function autoload(): bool
    {
        if (!$this->app->config->get('addons.autoload', true)) {
            return true;
        }
        $config = $this->app->config->get('addons');
        $base = get_class_methods("\\think\\Addons");
        foreach (glob($this->getAddonsPath() . '*/*.php') as $addons_file) {
            $info = pathinfo($addons_file);
            $name = pathinfo($info['dirname'], PATHINFO_FILENAME);
            if (strtolower($info['filename']) === 'plugin') {
                $methods = (array)get_class_methods("\\addons\\" . $name . "\\" . $info['filename']);
                $hooks = array_diff($methods, $base);
                foreach ($hooks as $hook) {
                    if (!isset($config['hooks'][$hook])) {
                        $config['hooks'][$hook] = [];
                    }
                    if (is_string($config['hooks'][$hook])) {
                        $config['hooks'][$hook] = explode(',', $config['hooks'][$hook]);
                    }
                    if (!in_array($name, $config['hooks'][$hook])) {
                        $config['hooks'][$hook][] = $name;
                    }
                }
            }
        }
        $this->app->config->set($config, 'addons');
        return true;
    }

    /**
     * 加载插件命令
     */
    private function loadCommand(): void
    {
        static $loaded = false;
        if ($loaded) {
            return;
        }
        $results = scandir($this->addons_path);
        foreach ($results as $name) {
            if ($name === '.' or $name === '..') {
                continue;
            }
            if (is_file($this->addons_path . $name)) {
                continue;
            }
            $addonDir = $this->addons_path . $name . DIRECTORY_SEPARATOR;
            if (!is_dir($addonDir)) {
                continue;
            }
            $command_file = $addonDir . 'command.php';
            if (is_file($command_file)) {
                $commands = include_once $command_file;
                if (is_array($commands)) {
                    $this->commands($commands);
                }
            }
        }
        $loaded = true;
    }

    /**
     * 注册插件自动加载器
     */
    private function registerAutoloader(): void
    {
        spl_autoload_register([$this, 'autoloadClass']);
    }

    /**
     * 自动加载插件类
     * @param string $class 类名
     * @return bool
     */
    private function autoloadClass(string $class): bool
    {
        static $cache = [];
        if (isset($cache[$class])) {
            return $cache[$class];
        }

        $class = ltrim($class, '\\');
        $dir = $this->app->getRootPath();
        $namespace = 'addons';

        if (strpos($class, $namespace) === 0) {
            $class = substr($class, strlen($namespace));
            $path = '';
            if (($pos = strripos($class, '\\')) !== false) {
                $path = str_replace('\\', '/', substr($class, 0, $pos)) . '/';
                $class = substr($class, $pos + 1);
            }
            $path .= str_replace('_', '/', $class) . '.php';
            $dir .= $namespace . $path;

            if (file_exists($dir)) {
                include $dir;
                $cache[$class] = true;
                return true;
            }
        }

        $cache[$class] = false;
        return false;
    }

    /**
     * 获取 addons 路径
     * @return string
     */
    public function getAddonsPath(): string
    {
        $addons_path = $this->app->getRootPath() . 'addons' . DIRECTORY_SEPARATOR;
        if (!is_dir($addons_path)) {
            if (!@mkdir($addons_path, 0755, true)) {
                throw new RuntimeException('Failed to create addons directory: ' . $addons_path);
            }
        }
        return $addons_path;
    }

    /**
     * 获取插件的配置信息
     * @return array
     */
    public function getAddonsConfig(): array
    {
        $name = $this->app->request->addon;
        if (empty($name)) {
            return [];
        }

        $addon = get_addons_instance($name);
        if (!$addon) {
            return [];
        }

        return $addon->getConfig() ?: [];
    }
}
